package com.google.code.timetrail.tests;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Random;

import org.junit.Before;
import org.junit.Test;

import com.google.code.timetrail.backend.*;

/**
 * 
 * @author Steven Miller
 *
 */
public class ControlTest {
	
	private static final int TEST_FOOD_AMOUNT = 30000000;
	
	private static final int EVENT_TEST_ITERATIONS = 1000;
	
	private static final int TAKE_A_TURN_TEST_ITERATIONS =1000;
	
	private static final int POSSIBLE_DEAD_MEMBERS = 20;
	
	private Person dr,engie,entre,nut;
	
	private Item eng,flux,food,fcell,hpart,money,tbb,tsuit;
	
	private Control contr;
	

	private Inventory fullInv;
	
	private Place[] places;
	
	private TravelSpeed[] speeds;
	
	private ConsumptionRate[] rations;
	
	private Random rng;
	@Before
	public void setUpControl() {
		
		dr = new Doctor();
		dr.setName("Dr. Dick");
		engie = new Engineer();
		engie.setName("Gottam");
		entre = new Entrepreneur();
		entre.setName("Tex Richman");
	    nut = new Nutritionist();
		nut.setName("Health Man-Man");
		
		eng = new Engine();
		flux = new FluxCapacitor();
		food = new Food();
		fcell = new FuelCell();
		hpart = new HullPart();
		money = new Money();
		tbb = new TimeBulletBill();
		tsuit = new TimeSuit();
		
		contr = new Control();
		
		fullInv = new Inventory();
		
		fullInv.setInvWeight(Integer.MIN_VALUE);
		fullInv.addItem(eng.getMaxAmount(), eng);
		fullInv.addItem(flux.getMaxAmount(),flux);
		fullInv.addItem(TEST_FOOD_AMOUNT,food);
		fullInv.addItem(fcell.getMaxAmount(), fcell);
		fullInv.addItem(hpart.getMaxAmount(), hpart);
		fullInv.addItem(money.getMaxAmount(), money);
		fullInv.addItem(tbb.getMaxAmount(), tbb);
		fullInv.addItem(tsuit.getMaxAmount(), tsuit);
		
		places = Place.values();
		speeds = TravelSpeed.values();
		rations = ConsumptionRate.values();
		
		rng = new Random();
		
		/*
		 * NOTE: The extremely negative weight in fullInv is there to support
		 * a huge amount of food to be held in the inventory because inventory 
		 * capacity caps are not something we're testing in this test
		 */
	}
	
	@Test
	public void testTakeATurnDoctor() {
		/*structure:test for party sizes up to 1000
		 * with random pace and rations using the doctor 
		 * profession
		 */
		contr.setPlayer(dr);
		ArrayList<Person> members = new ArrayList<Person>();
		int foodCount,distanceTraveled,placePointer;
		
		placePointer = 1;
		
		for(int i = 0; i<TAKE_A_TURN_TEST_ITERATIONS;i++){
			//get the prior food amount in the inventory/distance traveled
			//and inventory weight
			foodCount = contr.getInv().getFood().getCurrentAmount();
			distanceTraveled = contr.getTime().getTimeTraveled();
			
			//change the pace and rations
			contr.setRations(rations[rng.nextInt(rations.length)]);
			contr.setPace(speeds[rng.nextInt(speeds.length)]);
			
			//check that the last place was properly updated
			assertEquals("Error Occured cycle: " + i,contr.getLastPlace(),places[placePointer]);
			
			contr.takeATurn();
			
			//check if inventory removed the right number of food
			if(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon() < 0) {
				assertEquals(0,contr.getInv().getFood().getCurrentAmount());
			}
			else {
				assertEquals(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon(), contr.getInv().getFood().getCurrentAmount());
			}
			//check if distance changed correctly (be sure to compare to place)
			if(contr.getLastPlace() != places[placePointer]){
				assertEquals("Error Occured cycle: " + i,places[++placePointer].getLocation(),contr.getTime().getTimeTraveled());
			}
			else if(places[placePointer] == Place.OREGON) {
				assertEquals(Place.OREGON.getLocation(),contr.getTime().getTimeTraveled());
			}
			else {
				assertEquals(distanceTraveled+contr.getPace().getPerTurnDistance(),contr.getTime().getTimeTraveled());
			}
			members.add(new Person());
		}
	}
	
	@Test
	public void testTakeATurnEngineer() {
		/*structure:test for party sizes up to 1000
		 * with random pace and rations using the engineer
		 * profession
		 */
		contr.setPlayer(engie);
		ArrayList<Person> members = new ArrayList<Person>();
		int foodCount,distanceTraveled,placePointer;
		
		placePointer = 1;
		
		for(int i = 0; i<TAKE_A_TURN_TEST_ITERATIONS;i++){
			//get the prior food amount in the inventory/distance traveled
			//and inventory weight
			foodCount = contr.getInv().getFood().getCurrentAmount();
			distanceTraveled = contr.getTime().getTimeTraveled();
			
			//change the pace and rations
			contr.setRations(rations[rng.nextInt(rations.length)]);
			contr.setPace(speeds[rng.nextInt(speeds.length)]);
			
			//check that the last place was properly updated
			assertEquals("Error Occured cycle: " + i,contr.getLastPlace(),places[placePointer]);
			
			contr.takeATurn();
			
			//check if inventory removed the right number of food
			if(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon() < 0) {
				assertEquals(0,contr.getInv().getFood().getCurrentAmount());
			}
			else {
				assertEquals(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon(), contr.getInv().getFood().getCurrentAmount());
			}
			//check if distance changed correctly (be sure to compare to place)
			if(contr.getLastPlace() != places[placePointer]){
				assertEquals("Error Occured cycle: " + i,places[++placePointer].getLocation(),contr.getTime().getTimeTraveled());
			}
			else if(places[placePointer] == Place.OREGON) {
				assertEquals(Place.OREGON.getLocation(),contr.getTime().getTimeTraveled());
			}
			else {
				assertEquals(distanceTraveled+contr.getPace().getPerTurnDistance(),contr.getTime().getTimeTraveled());
			}
			members.add(new Person());
		}
	}
	
	@Test
	public void testTakeATurnEntrepreneur() {
		/*structure:test for party sizes up to 1000
		 * with random pace and rations using the entrepreneur 
		 * profession
		 */
		contr.setPlayer(entre);
		ArrayList<Person> members = new ArrayList<Person>();
		int foodCount,distanceTraveled,placePointer;
		
		placePointer = 1;
		
		for(int i = 0; i<TAKE_A_TURN_TEST_ITERATIONS;i++){
			//get the prior food amount in the inventory/distance traveled
			//and inventory weight
			foodCount = contr.getInv().getFood().getCurrentAmount();
			distanceTraveled = contr.getTime().getTimeTraveled();
			
			//change the pace and rations
			contr.setRations(rations[rng.nextInt(rations.length)]);
			contr.setPace(speeds[rng.nextInt(speeds.length)]);
			
			//check that the last place was properly updated
			assertEquals("Error Occured cycle: " + i,contr.getLastPlace(),places[placePointer]);
			
			contr.takeATurn();
			
			//check if inventory removed the right number of food
			if(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon() < 0) {
				assertEquals(0,contr.getInv().getFood().getCurrentAmount());
			}
			else {
				assertEquals(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon(), contr.getInv().getFood().getCurrentAmount());
			}
			//check if distance changed correctly (be sure to compare to place)
			if(contr.getLastPlace() != places[placePointer]){
				assertEquals("Error Occured cycle: " + i,places[++placePointer].getLocation(),contr.getTime().getTimeTraveled());
			}
			else if(places[placePointer] == Place.OREGON) {
				assertEquals(Place.OREGON.getLocation(),contr.getTime().getTimeTraveled());
			}
			else {
				assertEquals(distanceTraveled+contr.getPace().getPerTurnDistance(),contr.getTime().getTimeTraveled());
			}
			members.add(new Person());
		}
	}
	
	@Test
	public void testTakeATurnNutritionist() {
		/*structure:test for party sizes up to 1000
		 * with random pace and rations using the Nutritionist 
		 * profession
		 */
		contr.setPlayer(nut);
		ArrayList<Person> members = new ArrayList<Person>();
		int foodCount,distanceTraveled,placePointer;
		
		placePointer = 1;
		
		for(int i = 0; i<TAKE_A_TURN_TEST_ITERATIONS;i++){
			//get the prior food amount in the inventory/distance traveled
			//and inventory weight
			foodCount = contr.getInv().getFood().getCurrentAmount();
			distanceTraveled = contr.getTime().getTimeTraveled();
			
			//change the pace and rations
			contr.setRations(rations[rng.nextInt(rations.length)]);
			contr.setPace(speeds[rng.nextInt(speeds.length)]);
			
			//check that the last place was properly updated
			assertEquals("Error Occured cycle: " + i,contr.getLastPlace(),places[placePointer]);
			
			contr.takeATurn();
			
			//check if inventory removed the right number of food
			if(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon() < 0) {
				assertEquals(0,contr.getInv().getFood().getCurrentAmount());
			}
			else {
				assertEquals(foodCount-(members.size()+1)*contr.getRations().getPerPersonCon(), contr.getInv().getFood().getCurrentAmount());
			}
			//check if distance changed correctly (be sure to compare to place)
			if(contr.getLastPlace() != places[placePointer]){
				assertEquals("Error Occured cycle: " + i,places[++placePointer].getLocation(),contr.getTime().getTimeTraveled());
			}
			else if(places[placePointer] == Place.OREGON) {
				assertEquals(Place.OREGON.getLocation(),contr.getTime().getTimeTraveled());
			}
			else {
				assertEquals(distanceTraveled+contr.getPace().getPerTurnDistance(),contr.getTime().getTimeTraveled());
			}
			members.add(new Person());
		}
	}
	
	@Test
	public void testTakeATurnDeadMembers() {
		// A variation of TakeATurnNutritionist that adds dead members to the mix
		contr.setPlayer(nut);
		ArrayList<Person> members = new ArrayList<Person>();
		int foodCount,distanceTraveled,placePointer;
		int numOfMembers;
		placePointer = 1;
		
		for(int i = 0; i<TAKE_A_TURN_TEST_ITERATIONS;i++){
			numOfMembers = rng.nextInt(POSSIBLE_DEAD_MEMBERS);
			for(int j = 0; j < numOfMembers; j++) {
				members.add(new Person());
				if(rng.nextBoolean()){
					members.get(j).die();
				}
			}
			//get the prior food amount in the inventory/distance traveled
			//and inventory weight
			foodCount = contr.getInv().getFood().getCurrentAmount();
			distanceTraveled = contr.getTime().getTimeTraveled();
			
			//change the pace and rations
			contr.setRations(rations[rng.nextInt(rations.length)]);
			contr.setPace(speeds[rng.nextInt(speeds.length)]);
			
			//check that the last place was properly updated
			assertEquals("Error Occured cycle: " + i,contr.getLastPlace(),places[placePointer]);
			
			contr.takeATurn();
			
			for(int k = 0; k < members.size();k++) {
				if(members.get(k).isAlive()){
					foodCount -= contr.getRations().getPerPersonCon();
				}
			}
			//check if inventory removed the right number of food
			if(foodCount-contr.getRations().getPerPersonCon() < 0) {
				assertEquals(0,contr.getInv().getFood().getCurrentAmount());
			}
			else {
				assertEquals(foodCount-contr.getRations().getPerPersonCon(), contr.getInv().getFood().getCurrentAmount());
			}
			//check if distance changed correctly (be sure to compare to place)
			if(contr.getLastPlace() != places[placePointer]){
				assertEquals("Error Occured cycle: " + i,places[++placePointer].getLocation(),contr.getTime().getTimeTraveled());
			}
			else if(places[placePointer] == Place.OREGON) {
				assertEquals(Place.OREGON.getLocation(),contr.getTime().getTimeTraveled());
			}
			else {
				assertEquals(distanceTraveled+contr.getPace().getPerTurnDistance(),contr.getTime().getTimeTraveled());
			}
			members.clear();
		}
	}
	
	@Test
	public void testGetRandomEvent() {
		//runs the get random event for a specified 
		//amount of times and checks if an event was
	    //returned
		for(int i = 0; i < EVENT_TEST_ITERATIONS; i++){
			assertTrue(contr.getRandomEvent() instanceof Event);
		}
		//apply random events to a full inventory
	}
	
}
